﻿using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Text;
using System.Linq;
using System.Threading.Tasks;
using System.Threading;

namespace Tnelab.MBWrapper
{
    class VipWebView : IWebView
    {
        readonly object js_call_native_event_arg_dic_lock = new object();
        readonly Dictionary<Int64, JsCallNativeEventArg> js_call_native_event_arg_dic = new Dictionary<long, JsCallNativeEventArg>();
        protected IntPtr WebView { get; set; }
        protected IntPtr ParentHandle { get; set; }

        public bool IsVip { get; private set; } = true;
        /// <summary>
        /// 获取webview大小
        /// </summary>
        
        bool is_transparent = false;
        /// <summary>
        /// webview是否透明
        /// </summary>
        public bool Transparent
        {
            get
            {
                return is_transparent;
            }
            set
            {
                is_transparent = value;
                MBVip.mbSetTransparent(WebView, value);
            }
        }
        public bool LoadUrlEndEnable { get; set; } = true;

        public Size Size => throw new NotImplementedException();

        public event EventHandler<LoadUrlBeginArgs> LoadUrlBegin;
        public event EventHandler<LoadUrlEndArgs> LoadUrlEnd;
        public event EventHandler<PaintBitUpdatedEventArg> PaintBitUpdated;
        public event EventHandler<JsCallNativeEventArg> JsCallNative;
        #region 私有方法
        MBVip.mbPaintBitUpdatedCallback PaintBitUpdatedCallback;
        unsafe void MbOnPaintBitUpdated()
        {
            if (PaintBitUpdatedCallback == null)
            {
                PaintBitUpdatedCallback = (IntPtr webView, IntPtr param, IntPtr buffer, MBVip.mbRect* r, int width, int height) =>
                {
                    if (this.PaintBitUpdated != null)
                    {
                        var arg = new PaintBitUpdatedEventArg();
                        arg.WebView = this;
                        arg.Width = width;
                        arg.Height = height;
                        //arg.Param = param;
                        arg.Buffer = new byte[4 * width * height];
                        Marshal.Copy(buffer, arg.Buffer, 0, arg.Buffer.Length);
                        arg.Rect = new Rect();
                        arg.Rect.X = r->x;
                        arg.Rect.Y = r->y;
                        arg.Rect.Width = r->w;
                        arg.Rect.Height = r->h;
                        this.PaintBitUpdated(this, arg);
                    }
                };
            }
            MBVip.mbOnPaintBitUpdated(WebView, PaintBitUpdatedCallback, IntPtr.Zero);
        }
        MBVip.mbLoadUrlBeginCallback LoadUrlBeginCallback;
        void MbOnLoadUrlBegin()
        {
            if (LoadUrlBeginCallback == null)
            {
                LoadUrlBeginCallback = (webView, param, url, job) => {
                    if (this.LoadUrlBegin != null)
                    {
                        var args = new LoadUrlBeginArgs();
                        args.WebView = this;
                        args.Url = new Uri(url);
                        args.Datas = new List<byte>();
                        this.LoadUrlBegin(this, args);
                        if (args.Datas.Count > 0)
                        {
                            var dataPtr = Marshal.AllocHGlobal(args.Datas.Count);
                            Marshal.Copy(args.Datas.ToArray(), 0, dataPtr, args.Datas.Count);
                            MBVip.mbNetSetData(job, dataPtr, args.Datas.Count);
                            Marshal.FreeHGlobal(dataPtr);
                        }
                        else
                        {
                            if (this.LoadUrlEndEnable)
                            {
                                MBVip.mbNetHookRequest(job);
                            }
                        }
                        return true;
                    }
                    return false;
                };
            }
            MBVip.mbOnLoadUrlBegin(WebView, LoadUrlBeginCallback, IntPtr.Zero);
        }
        MBVip.mbLoadUrlEndCallback LoadUrlEndCallback;
        void MbOnLoadUrlEnd()
        {
            if (LoadUrlEndCallback == null)
            {
                LoadUrlEndCallback = (webView, param, url, job, buf, len) =>
                {
                    if (this.LoadUrlEnd != null)
                    {
                        var args = new LoadUrlEndArgs();
                        args.WebView = this;
                        args.Url = new Uri(url);
                        var datas = new byte[len];
                        Marshal.Copy(buf, datas, 0, datas.Length);
                        args.Datas = datas;
                        args.NeedUpdate = false;
                        this.LoadUrlEnd(this, args);
                        if (args.NeedUpdate)
                        {
                            var dataPtr = Marshal.AllocCoTaskMem(args.Datas.Count);
                            Marshal.Copy((byte[])args.Datas, 0, dataPtr, args.Datas.Count);
                            MBVip.mbNetSetData(job, dataPtr, args.Datas.Count);
                            Marshal.FreeCoTaskMem(dataPtr);
                        }
                    }
                };
            }
            MBVip.mbOnLoadUrlEnd(WebView, LoadUrlEndCallback, IntPtr.Zero);
        }
        MBVip.mbJsQueryCallback JsQueryCallback;
        void MbJsQuery()
        {
            if (JsQueryCallback == null)
            {
                JsQueryCallback = (webView,  param,  es,  queryId,  customMsg,  request) => {
                    JsCallNativeEventArg args = new JsCallNativeEventArg();
                    args.MessageId = customMsg;
                    args.Request = request;
                    args.CallId = queryId;
                    args.WebView = webView;
                    lock (js_call_native_event_arg_dic_lock)
                    {
                        js_call_native_event_arg_dic.Add(queryId, args);
                    }
                    this.OnJsCallNative(args);
                };
            }
            MBVip.mbOnJsQuery(this.WebView, JsQueryCallback, IntPtr.Zero);
        }
        #endregion
        /// <summary>
        /// 构造WebView
        /// </summary>
        public VipWebView(IntPtr parentHandle, Action<Action> uiInvoke)
        {
            this.UIInvoke = uiInvoke;
            ParentHandle = parentHandle;            
            WebView = MBVip.mbCreateWebView();
            MBVip.mbSetHandle(WebView,ParentHandle);
            MbOnPaintBitUpdated();
            MbOnLoadUrlBegin();
            MbOnPaintBitUpdated();
            MbJsQuery();
        }
        protected void OnJsCallNative(JsCallNativeEventArg args)
        {
            if (this.JsCallNative != null)
            {
                Task.Run(() => { 
                    this.JsCallNative(this,args);
                });
            }
        }
        public void Resize(int width,int height)
        {
            MBVip.mbResize(WebView, width, height);
        }
        /// <summary>
        /// 加载URL
        /// </summary>
        /// <param name="url"></param>
        public void LoadUrl(Uri url)
        {
            MBVip.mbLoadURL(WebView, url.ToString());
        }
        /// <summary>
        /// 触发键盘按下消息
        /// </summary>
        /// <param name="virtualKeyCode">虚拟键值</param>
        /// <param name="flags"></param>
        /// <param name="systemKey">是否系统键</param>
        /// <returns></returns>
        public bool FireKeyDownEvent(uint virtualKeyCode, uint flags, bool systemKey)
        {
            return MBVip.mbFireKeyDownEvent(WebView, virtualKeyCode, flags, systemKey);
        }

        public bool FireKeyUpEvent(uint virtualKeyCode, uint flags, bool systemKey)
        {
            return MBVip.mbFireKeyUpEvent(WebView, virtualKeyCode, flags, systemKey);
        }

        public void SetFocus()
        {
            MBVip.mbSetFocus(WebView);
        }

        public void KillFocus()
        {
            MBVip.mbKillFocus(WebView);
        }

        public bool FireKeyPressEvent(uint virtualKeyCode, uint flags, bool systemKey)
        {
            return MBVip.mbFireKeyPressEvent(WebView, virtualKeyCode, flags, systemKey);
        }

        public bool FireMouseWheelEvent(int x, int y, int delta, uint flags)
        {
            return MBVip.mbFireMouseWheelEvent(WebView, x, y, delta, flags);
        }

        public bool FireMouseEvent(uint message, int x, int y, uint flags)
        {
            return MBVip.mbFireMouseEvent(WebView, message, x, y, flags);
        }

        public CursorType GetCursorType()
        {
            throw new NotImplementedException();
        }
        public Action<Action> UIInvoke { get; protected set; }
        public Task<string> RunJsAsync(string script)
        {
            TaskCompletionSource<string> tcs = new TaskCompletionSource<string>();
            Task.Run(() => {
                var frame = MBVip.mbWebFrameGetMainFrame(this.WebView);
                MBVip.mbRunJs(this.WebView, frame, script, true, (view, p, es, jv) => {
                    var rstr = Marshal.PtrToStringUTF8(MBVip.mbJsToString(es, jv));
                    tcs.SetResult(rstr);
                }, IntPtr.Zero, IntPtr.Zero);
            });
            return tcs.Task;
        }
        public void Dispose()
        {            
            Dispose(true);
            GC.SuppressFinalize(this);
        }
        bool is_disposed = false;
        protected void Dispose(bool disposing)
        {
            if (this.WebView == IntPtr.Zero)
                throw new Exception("资源释放异常WebView不能为0");
            if (!is_disposed)
            {
                if (disposing)
                {
                    //Clean Up managed resources  
                }
                MBVip.mbDestroyWebView(this.WebView);
            }
            is_disposed = true;
        }

        public void ResponseJsCallNative(long callId, int messageId, string response)
        {
            JsCallNativeEventArg args;
            lock (js_call_native_event_arg_dic_lock)
            {
                args = js_call_native_event_arg_dic[callId];
                js_call_native_event_arg_dic.Remove(callId);
            }
            MBVip.mbResponseQuery(args.WebView,callId, messageId, response);
        }
        void IWebView.JsExecStateInvoke(Action<IntPtr> action)
        {
            throw new NotImplementedException();
        }

        ~VipWebView()
        {
            Dispose(false);
        }
    }
}
